package jmine.tec.utils.annotation.visitor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * {@link ClassAnnotationReader} abstrato para servir de base
 * 
 * @author takeshi
 * @param <R> o tipo dos visitors
 */
public abstract class AbstractAnnotationReader<R> implements ClassAnnotationReader<R> {

    private final Set<ClassAnnotationVisitor<? extends R>> classVisitors = new HashSet<ClassAnnotationVisitor<? extends R>>();

    private final Set<MethodAnnotationVisitor<? extends R>> methodVisitors = new HashSet<MethodAnnotationVisitor<? extends R>>();

    private final Set<ParameterAnnotationVisitor<? extends R>> parameterVisitors = new HashSet<ParameterAnnotationVisitor<? extends R>>();

    private final Set<FieldAnnotationVisitor<? extends R>> fieldVisitors = new HashSet<FieldAnnotationVisitor<? extends R>>();

    private int flags;

    private static final int VISIT_CLASS = 1;

    private static final int VISIT_CONSTRUCTOR = 2;

    private static final int VISIT_METHODS = 4;

    private static final int VISIT_PARAMS = 8;

    private static final int VISIT_FIELDS = 16;

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#addClassAnnotationVisitor(jmine.tec.utils.annotation.visitor.ClassAnnotationVisitor)
     */
    public void addClassAnnotationVisitor(ClassAnnotationVisitor<? extends R> visitor) {
        this.classVisitors.add(visitor);
        flags |= VISIT_CLASS;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#addMethodAnnotationVisitor(jmine.tec.utils.annotation.visitor.MethodAnnotationVisitor)
     */
    public void addMethodAnnotationVisitor(MethodAnnotationVisitor<? extends R> visitor) {
        methodVisitors.add(visitor);
        flags |= VISIT_METHODS;
    }

    /**
     * {@inheritDoc}
     */
    public void addParameterAnnotationVisitor(ParameterAnnotationVisitor<? extends R> visitor) {
        this.parameterVisitors.add(visitor);
        flags |= VISIT_PARAMS;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#visitAll(java.lang.Class)
     */
    public List<? extends R> visitAll(Class<?> type) {
        List<R> collected = new ArrayList<R>();
        for (Class<?> clazz : selectTypes(type)) {
            collected.addAll(visitClassAnnotations(clazz));
            collected.addAll(visitAllMethodsAnnotations(clazz));
            collected.addAll(visitFieldsAnnotations(clazz));
        }
        return collected;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#visitClassAnnotations(java.lang.Class)
     */
    public List<? extends R> visitClassAnnotations(Class<?> type) {
        if ((flags & VISIT_CLASS) == 0) {
            return Collections.emptyList();
        }
        List<R> collected = new ArrayList<R>();
        Annotation[] annotations = type.getAnnotations();
        for (Annotation annotation : annotations) {
            for (ClassAnnotationVisitor<? extends R> visitor : this.classVisitors) {
                if (visitor.acceptsClassAnnotation(annotation, type)) {
                    Collection<? extends R> onAnnotation = visitor.onTypeAnnotation(annotation, type);
                    collected.addAll(onAnnotation);
                }
            }
        }
        return collected;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#visitConstructorAnnotation(java.lang.reflect.Constructor)
     */
    public List<? extends R> visitConstructorAnnotation(Constructor<?> ctor) {
        if ((flags & VISIT_CONSTRUCTOR) == 0) {
            return Collections.emptyList();
        }
        Annotation[] annotations = ctor.getAnnotations();
        Class<?> declaringClass = ctor.getDeclaringClass();
        List<R> collected = new ArrayList<R>();
        for (Annotation annotation : annotations) {
            for (MethodAnnotationVisitor<? extends R> visitor : this.methodVisitors) {
                if (visitor.acceptsConstructor(annotation, ctor, declaringClass)) {
                    collected.addAll(visitor.onConstructorAnnotation(annotation, declaringClass, ctor));
                }
            }
        }
        return collected;
    }

    /**
     * {@inheritDoc}
     */
    public List<? extends R> visitConstructorParametersAnnotation(Constructor<?> ctor) {
        if ((flags & VISIT_PARAMS) == 0) {
            return Collections.emptyList();
        }

        Class<?> declaringClass = ctor.getDeclaringClass();
        List<R> collected = new ArrayList<R>();
        Annotation[][] parameterAnnotations = ctor.getParameterAnnotations();
        Class<?>[] paramTypes = ctor.getParameterTypes();
        Type[] types = ctor.getGenericParameterTypes();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                for (ParameterAnnotationVisitor<? extends R> visitor : this.parameterVisitors) {
                    Class<?> paramterType = paramTypes[i];
                    Type genericType = types[i];
                    if (visitor.acceptsConstructorParameter(annotation, declaringClass, ctor, i, paramterType, genericType)) {
                        collected.addAll(visitor.onConstructorParameterAnnotation(annotation, declaringClass, ctor, i, paramterType,
                                genericType));
                    }
                }
            }
        }
        return collected;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#visitMethodAnnotation(java.lang.reflect.Method, Class)
     */
    public List<? extends R> visitMethodAnnotation(Method method, Class<?> declaringClass) {
        if ((flags & VISIT_METHODS) == 0) {
            return Collections.emptyList();
        }

        Annotation[] annotations = method.getAnnotations();
        List<R> collected = new ArrayList<R>();
        for (Annotation annotation : annotations) {
            for (MethodAnnotationVisitor<? extends R> visitor : this.methodVisitors) {
                if (visitor.acceptsMethod(annotation, method, declaringClass)) {
                    collected.addAll(visitor.onMethodAnnotation(annotation, declaringClass, method));
                }
            }
        }
        return collected;
    }

    /**
     * {@inheritDoc}
     */
    public List<? extends R> visitMethodParametersAnnotation(Method method, Class<?> declaringClass) {
        if ((flags & VISIT_PARAMS) == 0) {
            return Collections.emptyList();
        }
        List<R> collected = new ArrayList<R>();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Class<?>[] paramTypes = method.getParameterTypes();
        Type[] types = method.getGenericParameterTypes();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                for (ParameterAnnotationVisitor<? extends R> visitor : this.parameterVisitors) {
                    Class<?> parameterType = paramTypes[i];
                    Type genericType = types[i];
                    if (visitor.acceptsMethodParameter(annotation, declaringClass, method, i, parameterType, genericType)) {
                        collected.addAll(visitor.onMethodParameterAnnotation(annotation, declaringClass, method, i, parameterType,
                                genericType));
                    }
                }
            }
        }
        return collected;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#visitAllMethodsAnnotations(java.lang.Class)
     */
    public List<? extends R> visitAllMethodsAnnotations(Class<?> type) {
        if ((flags & -VISIT_CLASS - 1) == 0) {
            return Collections.emptyList();
        }
        Constructor<?>[] constructors = type.getConstructors();
        List<R> collected = new ArrayList<R>();
        for (Constructor<?> constructor : constructors) {
            collected.addAll(visitConstructorAnnotation(constructor));
            collected.addAll(visitConstructorParametersAnnotation(constructor));
        }
        for (Method m : selectMethods(type)) {
            collected.addAll(visitMethodAnnotation(m, type));
            collected.addAll(visitMethodParametersAnnotation(m, type));
        }
        return collected;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#addFieldAnnotationVisitor(jmine.tec.utils.annotation.visitor.FieldAnnotationVisitor)
     */
    public void addFieldAnnotationVisitor(FieldAnnotationVisitor<? extends R> visitor) {
        this.fieldVisitors.add(visitor);
        flags |= VISIT_FIELDS;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#visitFieldAnnotations(java.lang.Class, java.lang.reflect.Field)
     */
    public List<? extends R> visitFieldAnnotations(Class<?> type, Field field) {
        if ((flags & VISIT_FIELDS) == 0) {
            return Collections.emptyList();
        }
        List<R> collected = new ArrayList<R>();
        for (FieldAnnotationVisitor<? extends R> visitor : this.fieldVisitors) {
            Annotation[] annotations = field.getAnnotations();
            for (Annotation annotation : annotations) {
                if (visitor.acceptsFieldAnnotation(annotation, type, field)) {
                    collected.addAll(visitor.onFieldAnnotation(annotation, type, field));
                }
            }
        }
        return collected;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.utils.annotation.visitor.ClassAnnotationReader#visitFieldsAnnotations(java.lang.Class)
     */
    public List<? extends R> visitFieldsAnnotations(Class<?> type) {
        if ((flags & VISIT_FIELDS) == 0) {
            return Collections.emptyList();
        }
        List<R> collected = new ArrayList<R>();
        for (Field field : selectFields(type)) {
            collected.addAll(visitFieldAnnotations(type, field));
        }
        return collected;
    }

    /**
     * Coleta as interfaces
     * 
     * @param baseType {@link Class}
     * @param list {@link List}
     */
    protected void collectInterfaces(Class<?> baseType, List<Class<?>> list) {
        if (baseType == null) {
            return;
        }
        Class<?>[] interfaces = baseType.getInterfaces();
        if (interfaces != null) {
            for (Class<?> clazz : interfaces) {
                list.add(clazz);
            }
        }
        collectInterfaces(baseType.getSuperclass(), list);
    }

}
